#pragma once

enum
{
    HXN_REDRAWSTARTED,
    HXN_GETBYTEINFO,
    HXN_SETBYTE,
    HXN_SETNIBBLE,
    HXN_FILLRANGE,
    HXN_RCLICK,
    HXN_INSERTMODECHANGED,
    HXN_HOTADDRCHANGED,
    HXN_BASEADDRCHANGED,
    HXN_GROUPSIZECHANGED,
    HXN_SELCHANGED,
    HXN_CTRLKEYPRESSED,
    HXN_ENTERPRESSED,
    HXN_COPY,
    HXN_PASTE,
};

enum HXCOLUMN
{
    HX_COL_NONE = -1,
    HX_COL_ADDRESS,
    HX_COL_HEXDATA,
    HX_COL_ASCII
};

typedef struct HXBYTEINFO_S
{
    bool bHidden;
    bool bValid;
    uint8_t value;
    COLORREF color;
    COLORREF bkColor;

    bool operator==(const HXBYTEINFO_S & b) const
    {
        return memcmp(this, &b, sizeof(HXBYTEINFO_S)) == 0;
    }

    bool operator!=(const HXBYTEINFO_S & b) const
    {
        return memcmp(this, &b, sizeof(HXBYTEINFO_S)) != 0;
    }
} HXBYTEINFO;

template <>
struct std::hash<HXBYTEINFO>
{
    std::size_t operator()(const HXBYTEINFO & k) const
    {
        return (size_t)(k.bValid * 0xFFFFFFFF) ^ (k.value * 0x1010101) ^ k.color ^ k.bkColor;
    }
};

typedef struct
{
    NMHDR nmh;
    uint32_t address;
    size_t numBytes;
    bool bIgnoreDiff;
    HXBYTEINFO * oldBytes;
    HXBYTEINFO * newBytes;
} NMHXGETBYTEINFO;

typedef struct
{
    NMHDR nmh;
    bool bInsert;
    uint32_t address;
    uint8_t value;
} NMHXSETBYTE;

typedef struct
{
    NMHDR nmh;
    bool bInsert;
    uint32_t address;
    bool bLoNibble;
    uint8_t value;
} NMHXSETNIBBLE;

typedef struct
{
    NMHDR nmh;
    bool bInsert;
    uint32_t startAddress;
    uint32_t endAddress;
    uint8_t value;
} NMHXFILLRANGE;

typedef struct
{
    NMHDR nmh;
    uint32_t address;
    uint32_t length;
    uint8_t * data;
} NMHXSETBYTES;

typedef struct
{
    NMHDR nmh;
    uint32_t address;
} NMHXRCLICK;

typedef struct
{
    NMHDR nmh;
    uint32_t address;
    HXCOLUMN column;
} NMHXPASTE;

typedef struct
{
    NMHDR nmh;
    int nChar;
} NMHXCTRLKEYPRESSED;

class CHexEditCtrl :
    public CWindowImpl<CHexEditCtrl>
{
public:
    CHexEditCtrl(void);
    ~CHexEditCtrl(void);
    DECLARE_WND_CLASS(_T("HexEditCtrl"))
    BOOL Attach(HWND hWnd);
    HWND Detach(void);

    static char ByteAscii(uint8_t value);
    static uint8_t HexCharValue(char c);
    static int CALLBACK HaveFontCb(CONST LOGFONTW * lplf, CONST TEXTMETRICW * lptm, DWORD FontType, LPARAM lParam);
    static bool HaveFont(HDC hdc, const char * name);

    void Draw(void);

    void Copy(void);
    void Paste(bool bAdvanceCaret = true);

    void SetBaseAddress(uint32_t address);
    void SetByteGroupSize(int nBytes);

    uint32_t GetBaseAddress(void);
    uint32_t GetCaretAddress(void);
    uint32_t GetHotAddress(void);
    int GetNumBytesPerGroup(void);
    int GetNumBytesPerRow(void);
    int GetNumVisibleBytes(void);
    bool GetSelectionRange(uint32_t * startAddress, uint32_t * endAddress);
    HXCOLUMN GetFocusedColumn(void);
    bool GetInsertMode(void);

private:
    enum HXCELLSIDE
    {
        HX_LEFT,
        HX_RIGHT
    };

    enum
    {
        TIMER_ID_AUTO_REFRESH,
        TIMER_ID_DRAG_SCROLL
    };

    enum
    {
        BKCOLOR_DEFAULT = RGB(255, 255, 255),
        BKCOLOR_ADDR = RGB(220, 220, 220),
        COLOR_ADDR = RGB(40, 40, 40),
        BKCOLOR_SEL_FOCUSED = RGB(51, 153, 255),
        COLOR_SEL_FOCUSED = RGB(255, 255, 255),
        BKCOLOR_SEL_UNFOCUSED = RGB(200, 200, 200),
        COLOR_SEL_UNFOCUSED = RGB(0, 0, 0),
        BKCOLOR_HOT = RGB(140, 140, 140)
    };

    typedef struct
    {
        HXCOLUMN column;
        uint32_t asciiAddress;
        HXCELLSIDE asciiCellSide;
        uint32_t hexAddress;
        HXCELLSIDE hexCellSide;
    } HXHITTEST;

    typedef struct
    {
        CRect rcHex;
        CRect rcAscii;
    } HXRECTPAIR;

    uint32_t m_BaseAddress;
    uint32_t m_DrawnBaseAddress;
    uint32_t m_SelStartAddress;
    HXCELLSIDE m_SelStartCellSide;
    uint32_t m_SelEndAddress;
    HXCELLSIDE m_SelEndCellSide;
    uint32_t m_RealSelStartAddress;
    uint32_t m_RealSelEndAddress;
    bool m_bHaveRealSel;
    bool m_bInsertMode;
    bool m_bHaveCaret;
    bool m_bCaretVisible;
    uint32_t m_CaretAddress;
    bool m_bCaretLoNibble;
    bool m_bShowHotAddress;
    uint32_t m_HotAddress;
    HXCOLUMN m_FocusedColumn;
    HFONT m_Font;
    HBITMAP m_BackBMP;
    HDC m_BackDC;
    HCURSOR m_hCursorIBeam;
    HCURSOR m_hCursorDefault;
    int m_DragScrollDelta;
    bool m_bDblClicked;
    bool m_bLButtonDown;
    bool m_bMouseDragging;
    bool m_bLayoutChanged;
    int m_CharWidth;
    int m_CharHeight;
    CRect m_AddressColumnRect;
    CRect m_HexDataColumnRect;
    CRect m_AsciiColumnRect;
    int m_NumBytesPerGroup;
    int m_NumByteGroupsPerRow;
    int m_NumBytesPerRow;
    int m_NumVisibleRows;
    int m_NumVisibleBytes;
    HXBYTEINFO * m_NewBytes;
    HXBYTEINFO * m_OldBytes;

    static COLORREF BlendColor(COLORREF c1, COLORREF c2);
    static uint32_t SatAdd32(uint32_t a, uint32_t b);
    static uint32_t SatAdd32(uint32_t a, int b);

    void DrawAddressColumn(void);
    void DrawHeader(void);
    void Text(int x, int y, const char * text, COLORREF bg, COLORREF fg, CRect * rcOut);
    bool IsSelected(uint32_t address);
    int GetSelDirection(void);
    void CancelSelection(void);
    void SelectAllVisible(void);
    void UpdateRealSelection(void);

    uint32_t LineAddress(uint32_t address);

    void GetHexCellPos(int index, CRect * rc);
    void GetAsciiCellPos(int index, CRect * rc);
    void HitTest(int x, int y, HXHITTEST * pht);

    void ShowCaret(void);
    void HideCaret(void);
    void CaretIncrementNibble(void);
    void CaretDecrementNibble(void);
    bool UpdateCaretUI(bool bEnsureVisible, bool bTop = false);
    void EnsureCaretAddressVisible(bool bTop = false);
    bool IsCaretAddressVisible(void);

    void UpdateLayoutInfo(void);
    void ReallocByteBuffers(void);

    LRESULT Notify(UINT code);
    LRESULT NotifyGetByteInfo(uint32_t address, size_t numBytes, bool bIgnoreDiff, HXBYTEINFO * oldBytes, HXBYTEINFO * newBytes);
    LRESULT NotifySetByte(uint32_t address, uint8_t value);
    LRESULT NotifySetNibble(uint32_t address, bool bLoNibble, uint8_t value);
    LRESULT NotifyFillRange(uint32_t startAddress, uint32_t endAddress, uint8_t value);
    LRESULT NotifyCtrlKeyPressed(int nChar);
    LRESULT NotifyPaste(uint32_t address);
    LRESULT NotifyRightClick(uint32_t address);

    void OnLButtonDown(UINT nFlags, CPoint point);
    void OnLButtonDblClk(UINT nFlags, CPoint point);
    void OnLButtonUp(UINT nFlags, CPoint point);
    void OnRButtonDown(UINT nFlags, CPoint point);
    void OnRButtonUp(UINT nFlags, CPoint point);
    void OnMouseMove(UINT nFlags, CPoint point);
    BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
    void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    void OnSetFocus(CWindow wndOld);
    void OnKillFocus(CWindow wndFocus);
    void OnTimer(UINT_PTR nIDEvent);
    UINT OnGetDlgCode(LPMSG lpMsg);
    void OnPaint(CDCHandle dc);
    BOOL OnSetCursor(CWindow wnd, UINT nHitTest, UINT message);
    void OnWindowPosChanged(LPWINDOWPOS lpWndPos);

    BEGIN_MSG_MAP_EX(CHexEditCtrl)
    {
        MSG_WM_RBUTTONDOWN(OnRButtonDown);
        MSG_WM_LBUTTONDOWN(OnLButtonDown);
        MSG_WM_LBUTTONUP(OnLButtonUp);
        MSG_WM_RBUTTONUP(OnRButtonUp);
        MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk);
        MSG_WM_MOUSEMOVE(OnMouseMove);
        MSG_WM_MOUSEWHEEL(OnMouseWheel);
        MSG_WM_KEYDOWN(OnKeyDown);
        MSG_WM_CHAR(OnChar);
        MSG_WM_SETFOCUS(OnSetFocus);
        MSG_WM_KILLFOCUS(OnKillFocus);
        MSG_WM_TIMER(OnTimer);
        MSG_WM_GETDLGCODE(OnGetDlgCode);
        MSG_WM_PAINT(OnPaint);
        MSG_WM_SETCURSOR(OnSetCursor);
        MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged);
    }
    END_MSG_MAP()
};
